﻿using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Net;
using System.Windows;
using System.Windows.Resources;
using Microsoft.Phone.Reactive;

namespace MC.Phone.Ads.Rx
{
    public static class ObservableResource
    {
        private const string CacheFolder = "AdCache";

        public static void CacheResource(MemoryStream toCache, string cacheKey)
        {
            string targetFile = Path.Combine(CacheFolder, cacheKey);
            using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
            {
                try
                {
                    if (!isoFile.DirectoryExists(CacheFolder))
                        isoFile.CreateDirectory(CacheFolder);
                    if (isoFile.FileExists(targetFile))
                        isoFile.DeleteFile(targetFile);
                    using (IsolatedStorageFileStream outputStream = isoFile.CreateFile(targetFile))
                    {
                        toCache.WriteTo(outputStream);
                    }
                }
                catch (IsolatedStorageException)
                {
                }
            }
        }

        public static IObservable<MemoryStream> FromCache(string cacheKey)
        {
            return new AnonymousObservable<MemoryStream>(
                observer =>
                    {
                        string targetFile = Path.Combine(CacheFolder, cacheKey);

                        using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
                        {
                            if (!isoFile.FileExists(targetFile))
                            {
                                observer.OnError(
                                    new FileNotFoundException(string.Format("Cache empty for key:{0}", cacheKey)));
                            }
                            else
                            {
                                using (IsolatedStorageFileStream inputStream =
                                    isoFile.OpenFile(targetFile, FileMode.Open, FileAccess.Read))
                                {
                                    observer.OnNext(inputStream.ToMemoryStream());
                                }
                            }
                        }
                        observer.OnCompleted();
                        return Disposable.Empty;
                    });
        }

        public static IObservable<MemoryStream> FromResource(Uri applicationResourceUri)
        {
            return new AnonymousObservable<MemoryStream>(
                observer =>
                    {
                        StreamResourceInfo streamResourceInfo = Application.GetResourceStream(applicationResourceUri);
                        if (streamResourceInfo != null)
                            observer.OnNext(streamResourceInfo.Stream.ToMemoryStream());
                        else
                        {
                            string assemblyName = Application.Current.GetType().Assembly.FullName.Split(new[] {','})[0];
                            string newUri = string.Format("{0}{1}",
                                                          applicationResourceUri.ToString().StartsWith("/") ? null : "/",
                                                          applicationResourceUri);
                            string changeUri =
                                string.Format(
                                    "Consider setting the Build Action of \"{0}\" to Resource and change the Uri to \"{2};component{1}\"",
                                    applicationResourceUri, newUri, assemblyName);
                            string message = string.Format("{0} {2}{1}",
                                                           string.Format(
                                                               "The application resource \"{0}\" could not be found.",
                                                               applicationResourceUri),
                                                           Environment.NewLine, changeUri);
                            observer.OnError(new Exception(message));
                        }
                        observer.OnCompleted();
                        return Disposable.Empty;
                    });
        }

        public static IObservable<MemoryStream> FromInternet(Uri onlineResource, ICredentials credentials = null)
        {
            return
                new AnonymousObservable<WebResponse>(
                    observer =>
                        {
                            var httpWebRequest =
                                (HttpWebRequest) WebRequest.Create(onlineResource);
                            if (credentials != null)
                                httpWebRequest.Credentials = credentials;
                            httpWebRequest.BeginGetResponse(
                                iar =>
                                    {
                                        WebResponse response;
                                        try
                                        {
                                            var requestState = (HttpWebRequest) iar.AsyncState;
                                            response = requestState.EndGetResponse(iar);
                                        }
                                        catch (Exception exception)
                                        {
                                            observer.OnError(exception);
                                            return;
                                        }
                                        observer.OnNext(response);
                                        observer.OnCompleted();
                                    }, httpWebRequest);
                            return Disposable.Empty;
                        })
                    .Select(
                        response => response.GetResponseStream().ToMemoryStream());
        }

        public static MemoryStream ToMemoryStream(this Stream stream)
        {
            var br = new BinaryReader(stream);
            var outputStream = new MemoryStream();
            var buffer = new byte[1024];
            int cb;
            while ((cb = br.Read(buffer, 0, buffer.Length)) > 0)
            {
                outputStream.Write(buffer, 0, cb);
            }
            outputStream.Position = 0;
            return outputStream;
        }
    }

    public class MemoryStreamWithOrigin
    {
        #region StreamOrigin enum

        public enum StreamOrigin
        {
            Local,
            Remote,
            Cache
        }

        #endregion

        public MemoryStreamWithOrigin(MemoryStream stream, StreamOrigin origin)
        {
            Stream = stream;
            Origin = origin;
        }

        public MemoryStream Stream { get; private set; }
        public StreamOrigin Origin { get; private set; }
    }
}